import type { Encoding } from '@/types/common'
import { getVarType } from '@/utils/common'
import { numberToBuffer, bufferToNumber } from '@/utils/buffer'

/**
 * 消息包
 */
export class Packet {
  /**
   * 消息头编码
   */
  static readonly HEADER_ENCODING: Encoding = 'utf-8'

  /**
   * 消息头长度字节的长度
   */
  static readonly HEADER_BYTE_LENGTH = 4

  /**
   * 消息头最大长度
   */
  static readonly HEADER_MAX_LENGTH = 4294967296

  /**
   * 默认消息体编码
   */
  static readonly DEFAULT_BODY_ENCODING: Encoding = 'utf-8'

  /**
   * 错误消息内容编码
   */
  static readonly ERROR_CONTENT_ENCODING: Encoding = 'utf-8'

  /**
   * 不可编辑的消息头
   */
  static readonly NON_EDITABLE_HEADER: NonEditableHeader[] = ['content-length']

  static createErrorPacket(message?: string): Packet {
    const p = new Packet({
      error: true
    })

    if (message) p.setBodyByText(message, Packet.ERROR_CONTENT_ENCODING)

    return p
  }

  static createFatalErrorPacket(message?: string): Packet {
    const p = new Packet({
      fatal_error: true
    })

    if (message) p.setBodyByText(message, Packet.ERROR_CONTENT_ENCODING)

    return p
  }

  static createTimeoutPacket(): Packet {
    return new Packet({
      timeout: true
    })
  }

  static createPontPacket(): Packet {
    return new Packet({
      pong: true
    })
  }

  static createOkPacket(): Packet {
    return new Packet({
      ok: true
    })
  }

  static isPacket(p: any): p is Packet {
    return p instanceof Packet
  }

  constructor()

  constructor(header: ParamHeader)

  constructor(body: BodyType)

  constructor(header: ParamHeader, body: BodyType)

  constructor(...args: any[]) {
    let header: HeaderType | null = null
    let body: BodyType = null

    if (args.length === 1) {
      if (getVarType(args[0]) === 'object') {
        header = args[0]
      } else if (args[0] instanceof Buffer) {
        body = args[0]
      }
    } else if (args.length === 2) {
      if (getVarType(args[0]) === 'object') {
        header = args[0]
      }

      if (args[1] instanceof Buffer) {
        body = args[1]
      }
    }

    if (header) {
      this.setHeaders(header)
    }

    if (body) {
      this.setBody(body)
    }
  }

  private _header: HeaderType = {
    'content-length': 0
  }

  private _body: BodyType = null

  /**
   * 消息头
   */
  get header() {
    return this._header
  }

  /**
   * 消息体
   */
  get body() {
    return this._body
  }

  setHeader<T extends keyof EditableHeader>(name: T, value: Required<EditableHeader>[T]): this
  setHeader(name: string, value: HeaderValueType): this

  setHeader(name: string, value: HeaderValueType) {
    if (Packet.NON_EDITABLE_HEADER.includes(name as NonEditableHeader)) return
    this._header[name] = value
    return this
  }

  setHeaders(headers: ParamHeader) {
    if (getVarType(headers) !== 'object') return
    for (const name in headers) {
      this.setHeader(name, headers[name] as HeaderValueType)
    }
    return this
  }

  /**
   * 设置消息体
   * @param data 数据
   */
  setBody(data: BodyType) {
    this._body = data

    if (data instanceof Buffer) {
      this.header['content-length'] = data.length
    } else {
      this.header['content-length'] = 0
    }
    return this
  }

  /**
   * 设置消息体为 json 格式
   * @param data 数据
   * @param charset 编码
   */
  setBodyByJson(data: any, charset: Encoding = Packet.DEFAULT_BODY_ENCODING) {
    this.setBody(Buffer.from(JSON.stringify(data), charset))
    this.setHeader('charset', charset)
    this.setHeader('content-type', 'json')
    return this
  }

  /**
   * 设置消息体为文本格式
   * @param text 文本
   * @param charset 编码
   */
  setBodyByText(text: string, charset: Encoding = Packet.DEFAULT_BODY_ENCODING) {
    this.setBody(Buffer.from(text, charset))
    this.setHeader('charset', charset)
    this.setHeader('content-type', 'text')
    return this
  }

  /**
   * 以 json 的方式读取 body
   * @param charset 编码
   */
  readBodyAsJson<T = any>(charset?: Encoding): T | null {
    if (!this.body || this.header['content-type'] !== 'json') return null

    if (!charset) {
      if (this.header.charset) {
        charset = this.header.charset
      } else {
        charset = Packet.DEFAULT_BODY_ENCODING
      }
    }

    return JSON.parse(this.body.toString(charset))
  }

  /**
   * 以文本格式读取 body
   * @param charset 编码
   */
  readBodyAsText(charset?: Encoding) {
    if (!this.body || this.header['content-type'] !== 'text') return null

    if (!charset) {
      if (this.header.charset) {
        charset = this.header.charset
      } else {
        charset = Packet.DEFAULT_BODY_ENCODING
      }
    }

    return this.body.toString(charset)
  }

  /**
   * 读取错误消息内容
   */
  readErrorContent() {
    if ((!this.isError() && !this.isFatalError()) || !this.body) return null
    return this.readBodyAsText(Packet.ERROR_CONTENT_ENCODING)
  }

  /**
   * 是否为错误消息
   */
  isError() {
    return !!this.header.error
  }

  /**
   * 是否为致命错误消息
   */
  isFatalError() {
    return !!this.header.fatal_error
  }

  /**
   * 是否为超时消息
   */
  isTimeout() {
    return !!this.header.timeout
  }

  /**
   * 是否为加密消息
   */
  isEncrypt() {
    return !!this.header.encrypt
  }

  /**
   * 是否为 ok 消息
   */
  isOk() {
    return !!this.header.ok
  }

  /**
   * 是否为 pong 消息
   */
  isPong() {
    return !!this.header.pong
  }

  /**
   * 编码消息包
   */
  encode() {
    const chunks: Buffer[] = []
    const headerString = JSON.stringify(this._header)
    const headerBuffer = Buffer.from(headerString, Packet.HEADER_ENCODING)
    const headerLengthBuffer = numberToBuffer(headerBuffer.length, Packet.HEADER_BYTE_LENGTH)
    chunks.push(headerLengthBuffer, headerBuffer)
    if (this._body) chunks.push(this._body)
    return Buffer.concat(chunks)
  }

  /**
   * 解析消息消息体
   */
  decodeBody<T = any>(): T | null {
    if (!this.body || !this.header['content-type']) return null

    switch (this.header['content-type']) {
      case 'json':
        return this.readBodyAsJson() as T
      case 'text':
        return this.readBodyAsText() as T
      case 'octet-stream':
        return this.body as T
      default:
        return null
    }
  }

  /**
   * 解析消息头长度
   */
  static parseHeaderLength(data: Buffer) {
    return bufferToNumber(data, Packet.HEADER_BYTE_LENGTH)
  }

  /**
   * 解码消息头
   */
  static decodeHeader(data: Buffer) {
    const headerLength = Packet.parseHeaderLength(data)

    if (data.length < headerLength) throw new Error('header length error')

    const headerBuffer = data.subarray(
      Packet.HEADER_BYTE_LENGTH,
      headerLength + Packet.HEADER_BYTE_LENGTH
    )
    const headerString = headerBuffer.toString(Packet.HEADER_ENCODING)
    try {
      const header = JSON.parse(headerString) as HeaderType
      return header
    } catch (error) {
      console.log(headerString)
      throw error
    }
  }

  /**
   * 解码消息包
   */
  static decode(data: Buffer) {
    const headerLength = Packet.parseHeaderLength(data)
    const header = Packet.decodeHeader(data)
    const headerTotalLength = headerLength + Packet.HEADER_BYTE_LENGTH

    if (!header) throw new Error('header error')

    if (data.length < headerTotalLength) return null

    return new Packet(header, data.subarray(headerTotalLength))
  }
}

/**
 * 消息体格式
 */
export type ContentType = 'json' | 'text' | 'octet-stream'

/**
 * 默认消息头
 */
export interface DefaultHeader {
  /**
   * 消息格式
   */
  'content-type'?: ContentType

  /**
   * 内容长度
   */
  'content-length': number

  /**
   * 消息编码
   */
  charset?: Encoding

  /**
   * 数字签名
   */
  sign?: string

  /**
   * 认证标签
   */
  tag?: string

  /**
   * 是否为错误消息
   */
  error?: true

  /**
   * 是否为致命错误消息
   */
  fatal_error?: true

  /**
   * 是否为超时消息
   */
  timeout?: true

  /**
   * 消息体是否加密
   */
  encrypt?: true

  /**
   * 是否为心跳消息
   */
  pong?: true

  /**
   * 是否为ok消息
   */
  ok?: true
}

/**
 * 消息头值类型
 */
export type HeaderValueType = string | number | boolean

/**
 * 自定义消息头
 */
export interface CustomHeader {
  [name: string]: HeaderValueType
}

/**
 * 消息头
 */
export type HeaderType = DefaultHeader & CustomHeader

/**
 * 不可编辑的消息头
 */
export type NonEditableHeader = Extract<keyof DefaultHeader, 'content-length'>

/**
 * 可编辑的消息头
 */
export type EditableHeader = Omit<DefaultHeader, NonEditableHeader>

/**
 * 消息头参数
 */
export type ParamHeader = Partial<EditableHeader & CustomHeader>

/**
 * 消息体
 */
export type BodyType = Buffer | null
